#include "Spark/Net/Timingwheel.h"
#include "Spark/Utils/Logger.h"
#include <assert.h>

TimingWheel::TimingWheel(EventLoop *loop,
                         size_t maxTimeout,
                         size_t ticksInterval,
                         size_t bucketsPerWheel)
    : loop_(loop),
      tickInterval_(ticksInterval),
      bucketsPerWheel_(bucketsPerWheel)
{
    assert(maxTimeout > 1);
    assert(tickInterval_ > 0);
    assert(bucketsPerWheel_ > 1);
    size_t maxTickNum = static_cast<size_t>(maxTimeout / tickInterval_ + 1);
    size_t ticksNum = bucketsPerWheel_;
    wheelsNum_ = 1;
    while (maxTickNum > ticksNum)
    {
        ++wheelsNum_;
        ticksNum *= bucketsPerWheel_;
    }
    wheels_.resize(wheelsNum_);
    for (size_t i = 0; i < wheelsNum_; ++i)
    {
        wheels_[i].resize(bucketsPerWheel_);
    }
    timerId_ =
        loop_->runEvery(std::chrono::milliseconds(tickInterval_), [this] {
            ++ticks_;
            size_t t = ticks_;
            size_t pow = 1;
            for (size_t i = 0; i < wheelsNum_; ++i)
            {
                if ((t % pow) == 0)
                {
                    BucketEntry tmp;
                    {
                        wheels_[i].front().swap(tmp);
                        wheels_[i].pop_front();
                        wheels_[i].push_back(BucketEntry());
                    }
                }
                pow *= bucketsPerWheel_;
            }
        });
}

TimingWheel::~TimingWheel()
{
    assert(loop_->isInLoopThread());
    loop_->disableTimer(timerId_);
    for (auto iter = wheels_.rbegin(); iter != wheels_.rend(); ++iter)
    {
        iter->clear();
    }
    LOG_TRACE << "~TimingWheel() !";
}

void TimingWheel::insertEntry(int delay, EntryPtr ep)
{
    if (!ep)
    {
        return;
    }
    if (delay <= 0)
    {
        return;
    }
    if (loop_->isInLoopThread())
    {
        insertEntryInLoop(delay, ep);
    }
    else
    {
        loop_->runInLoop([this, delay, ep] { insertEntryInLoop(delay, ep); });
    }
}

void TimingWheel::insertEntryInLoop(int delay, EntryPtr ep)
{
    assert(loop_->isInLoopThread());
    size_t bucketPos = static_cast<size_t>(delay / tickInterval_ + 1);
    size_t t = ticks_;
    for (size_t i = 0; i < wheelsNum_; ++i)
    {
        if (bucketPos <= bucketsPerWheel_)
        {
            wheels_[i][bucketPos - 1].insert(ep);
            break;
        }
        if (i < (wheelsNum_ - 1))
        {
            ep = std::make_shared<Defer>([this, bucketPos, i, t, ep] {
                if (bucketPos > 0)
                {
                    size_t ind = (bucketPos + (t % bucketsPerWheel_) - 1) %
                                 bucketsPerWheel_;
                    wheels_[i][ind].insert(ep);
                }
            });
        }
        else
        {
            wheels_[i][bucketsPerWheel_ - 1].insert(ep);
        }
        bucketPos = (bucketPos + (t % bucketsPerWheel_) - 1) / bucketsPerWheel_;
        t = t / bucketsPerWheel_;
    }
}